home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / admin / quotcmp < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  31.0 KB  |  882 lines

  1. #!/usr/local/bin/gawk -f
  2. #!/usr/bin/awk -f
  3. # @(#) quotcmp.gawk 1.1 96/06/17
  4. # 94/01/11 John H. DuBois III (john@armory.com)
  5. # 94/02/07 Allow use of . for current quot output; added total row;
  6. #          added all options
  7. # 94/03/09 Use gawk so - options can be given
  8. # 96/06/17 Understand quot -f output.  Added a option.
  9. BEGIN {
  10.     Name = "quotcmp"
  11.     Usage = "Usage: " Name " [-ahbr] <filename> ..."
  12.     ARGC = Opts(Name,Usage,"ahbrx",1)
  13.     if ("h" in Options) {
  14.     print \
  15. Name ": Compare output of two or more quot runs on same device.\n"\
  16. Usage "\n"\
  17. "where <filename> is the output from running quot.  If a filename of . is\n"\
  18. "given, the current quot output for the device named in the other files is\n"\
  19. "used.  If only one filename is given, a second filename of .  is assumed.\n"\
  20. "Usage is reported in 1K blocks.\n"\
  21. "Options:\n"\
  22. "-a: Report on all users, regardless of whether usage has changed or not.\n"\
  23. "-h: print this help.\n"\
  24. "-b: print blocks used only.\n"\
  25. "-r: print change ratio only."
  26.     exit(0)
  27.     }
  28.     Debug = "x" in Options    # not currently used.
  29.     if (ARGC < 3) {
  30.     ARGV[2] = "."
  31.     ARGC = 3
  32.     }
  33.     # If any . names given, get device name because we'll need it
  34.     for (arg = 1; arg < ARGC; arg++)
  35.     if (ARGV[arg] == ".") {
  36.         if (Device == "" && (Device = FindDevice(ARGC,ARGV)) == "") {
  37.         print "No quot output files given." | "cat 1>&2"
  38.         exit(1)
  39.         }
  40.         break
  41.     }
  42.     for (arg = 1; arg < ARGC; arg++) {
  43.     File = ARGV[arg]
  44.     if ((NewDevice = ReadData(File,arg,DUsage,Order,Device)) == "") {
  45.         printf "Could not read file %s\n",File | "cat 1>&2"
  46.         exit(1)
  47.     }
  48.     else if (OldDevice != "" && NewDevice != OldDevice)
  49.         printf "Warning: different device names found.\n"
  50.     sub(".*/","",File)
  51.     FLen = length(File)
  52.     if (FLen > MaxF)
  53.         MaxF = FLen
  54.     Files[arg] = File
  55.     }
  56.     Report(!("r" in Options),!("b" in Options),DUsage,"a" in Options)
  57.     exit(0)
  58. }
  59.  
  60. function FindDevice(argc,argv,  arg,File) {
  61.     for (arg = 1; arg < argc; arg++)
  62.     if ((File = argv[arg]) != ".") {
  63.         if ((getline < File) != 1) {
  64.         close(File)
  65.         printf "Could not read file %s\n",File | "cat 1>&2"
  66.         return ""
  67.         }
  68.         close(File)
  69.         sub(":.*","",$0)
  70.         return $0
  71.     }
  72.     return ""
  73. }
  74.  
  75. function ReadData(File,Ind,Usage,Order,FirstDev,  i,Device) {
  76.     if (File == ".") {
  77.     File = "/usr/bin/quot " FirstDev
  78.     if ((File | getline) != 1)
  79.         return ""
  80.     Device = $1
  81.     while ((File | getline) == 1) {
  82.         Order[Ind,++i] = $2
  83.         Usage[Ind,$2] = $1
  84.     }
  85.     }
  86.     else {
  87.     if ((getline < File) != 1)
  88.         return ""
  89.     Device = $1
  90.     while ((getline < File) == 1) {
  91.         # Use last field for user name; quot output may have 2 or 3
  92.         # fields depending on whether -f option is given.
  93.         Order[Ind,++i] = $NF
  94.         Usage[Ind,$NF] = $1
  95.     }
  96.     }
  97.     close(File)
  98.     return Device
  99. }
  100.  
  101. function Percent(a,b) {
  102.     return int(a/b*100) "%"
  103. }
  104.  
  105. # return whichever among the A and B report types are wanted
  106. function PrintAB(FmtA,FmtB,A,B,  ret) {
  107.     if (FmtA != "")
  108.     ret = sprintf(FmtA,A)
  109.     if (FmtB != "")
  110.     ret = ret sprintf(FmtB,B)
  111.     return ret
  112. }
  113.  
  114. function Report(PrintBlocks,PrintRatio,Usage,AllUsers,
  115. UserFmt,Dig,NumFmt,DiffFmt,arg,UserNum,User,OldUsage,NewUsage,Tot,Line) {
  116.     if (!PrintBlocks && !PrintRatio)
  117.     PrintBlocks = PrintRatio = 1
  118.     UserFmt = "%-8s"
  119.     printf UserFmt,"User"
  120.     if (PrintRatio)
  121.     if (PrintBlocks)
  122.         DiffFmt = " %5s"
  123.     else
  124.         DiffFmt = " %8s"
  125.     if (PrintBlocks) {
  126.     Dig = max(MaxF,length(sprintf("%d",Usage[1,Order[1,1]] / 2)))
  127.     Dig = max(Dig,4)
  128.     NumFmt = "%" Dig+1 "s"
  129.     printf NumFmt,Files[1]
  130.     }
  131.     else
  132.     printf DiffFmt,Files[1]
  133.     for (arg = 2; arg < ARGC; arg++)
  134.     if (!PrintBlocks)
  135.         printf DiffFmt,Files[arg]
  136.     else if (!PrintBlocks)
  137.         printf NumFmt,Files[arg]
  138.     else
  139.         printf NumFmt DiffFmt,Files[arg],"Ratio"
  140.     print ""
  141.     for (UserNum = 1; (1 SUBSEP UserNum) in Order; UserNum++) {
  142.     Diff = 0
  143.     User = Order[1,UserNum]
  144.     Line = sprintf(UserFmt,User)
  145.     Tot[1] += OldUsage = Usage[1,User]
  146.     # Print amount for first device
  147.     if (PrintBlocks)
  148.         Line = Line sprintf(NumFmt,int(OldUsage/2))
  149.     else
  150.         Line = Line sprintf(DiffFmt,"100%")
  151.     # Print amounts for other devices
  152.     for (arg = 2; arg < ARGC; arg++) {
  153.         if ((arg SUBSEP User) in Usage) {
  154.         Tot[arg] += NewUsage = Usage[arg,User]
  155.         Line = Line PrintAB(NumFmt,DiffFmt,int(NewUsage/2),
  156.         Percent(NewUsage,OldUsage))
  157.         if (NewUsage != OldUsage)
  158.             Diff = 1
  159.         }
  160.         else
  161.         Line = Line sprintf(NumFmt DiffFmt,"-","-")
  162.     }
  163.     if (Diff || AllUsers)
  164.         print Line
  165.     }
  166.     printf UserFmt,"Total"
  167.     if (PrintBlocks)
  168.     printf NumFmt,int(Tot[1]/2)
  169.     else
  170.     printf DiffFmt,"100%"
  171.     for (arg = 2; arg < ARGC; arg++) {
  172.     Old[arg] += NewUsage = Usage[arg,User]
  173.     printf "%s",
  174.     PrintAB(NumFmt,DiffFmt,int(Tot[arg]/2),Percent(Tot[arg],Tot[1]))
  175.     }
  176.     print ""
  177. }
  178.  
  179. function min(a,b) {
  180.     if (a < b)
  181.     return a
  182.     else
  183.     return b
  184. }
  185.  
  186. function max(a,b) {
  187.     if (a > b)
  188.     return a
  189.     else
  190.     return b
  191. }
  192.  
  193. function In(Val,Min,Max) {
  194.     return (Min <= Val && Val <= Max)
  195. }
  196.  
  197. ### Start of ProcArgs library
  198. # @(#) ProcArgs 1.11 96/12/08
  199. # 92/02/29 john h. dubois iii (john@armory.com)
  200. # 93/07/18 Added "#" arg type
  201. # 93/09/26 Do not count -h against MinArgs
  202. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  203. #          Removed meaning of "+" or "-" by itself.
  204. # 94/03/08 Added & option and *()< option types.
  205. # 94/04/02 Added NoRCopt to Opts()
  206. # 94/06/11 Mark numeric variables as such.
  207. # 94/07/08 Opts(): Do not require any args if h option is given.
  208. # 95/01/22 Record options given more than once.  Record option num in argv.
  209. # 95/06/08 Added ExclusiveOptions().
  210. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  211. #          Expand $VARNAME at the start of its filenames.
  212. #          Let varname=0 and -option- turn off an option.
  213. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  214. #          of the vars should be searched for in the environment.
  215. #          Check for duplicate rcfiles.
  216. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  217. #          now return various negatives values on error, not just -1, and
  218. #          Opts() may set Err to various positive values, not just 1.
  219. #          Added AllowUnrecOpt.
  220. # 96/05/23 Check type given for & option
  221. # 96/06/15 Re-port to awk
  222. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  223. #          used by other functions.
  224. # 96/10/15 Added OptChars
  225. # 96/11/01 Added exOpts arg to Opts()
  226. # 96/11/16 Added ; type
  227. # 96/12/08 Added Opt2Set() & Opt2Sets()
  228. # 96/12/27 Added CmdLineOpt()
  229.  
  230. # optlist is a string which contains all of the possible command line options.
  231. # A character followed by certain characters indicates that the option takes
  232. # an argument, with type as follows:
  233. # :    String argument
  234. # ;    Non-empty string argument
  235. # *    Floating point argument
  236. # (    Non-negative floating point argument
  237. # )    Positive floating point argument
  238. # #    Integer argument
  239. # <    Non-negative integer argument
  240. # >    Positive integer argument
  241. # The only difference the type of argument makes is in the runtime argument
  242. # error checking that is done.
  243.  
  244. # The & option is a special case used to get numeric options without the
  245. # user having to give an option character.  It is shorthand for [-+.0-9].
  246. # If & is included in optlist and an option string that begins with one of
  247. # these characters is seen, the value given to "&" will include the first
  248. # char of the option.  & must be followed by a type character other than ":"
  249. # or ";".
  250. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  251.  
  252. # Strings in argv[] which begin with "-" or "+" are taken to be
  253. # strings of options, except that a string which consists solely of "-"
  254. # or "+" is taken to be a non-option string; like other non-option strings,
  255. # it stops the scanning of argv and is left in argv[].
  256. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  257. # If an option takes an argument, the argument may either immediately
  258. # follow it or be given separately.
  259. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  260. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  261. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  262. # this feature had a flaw that caused problems in some cases.  See the OptChars
  263. # parameter to explicitly set the option-specifier characters.
  264.  
  265. # If an option that does not take an argument is given,
  266. # an index with its name is created in Options and its value is set to the
  267. # number of times it occurs in argv[].
  268.  
  269. # If an option that does take an argument is given, an index with its name is
  270. # created in Options and its value is set to the value of the argument given
  271. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  272. # If an option that takes an argument is given more than once,
  273. # Options[option-name,"count"] is incremented, and the value is assigned to
  274. # the index (option-name,instance) where instance is 2 for the second occurance
  275. # of the option, etc.
  276. # In other words, the first time an option with a value is encountered, the
  277. # value is assigned to an index consisting only of its name; for any further
  278. # occurances of the option, the value index has an extra (count) dimension.
  279.  
  280. # The sequence number for each option found in argv[] is stored in
  281. # Options[option-name,"num",instance], where instance is 1 for the first
  282. # occurance of the option, etc.  The sequence number starts at 1 and is
  283. # incremented for each option, both those that have a value and those that
  284. # do not.  Options set from a config file have a value of 0 assigned to this.
  285.  
  286. # Options and their arguments are deleted from argv.
  287. # Note that this means that there may be gaps left in the indices of argv[].
  288. # If compress is nonzero, argv[] is packed by moving its elements so that
  289. # they have contiguous integer indices starting with 0.
  290. # Option processing will stop with the first unrecognized option, just as
  291. # though -- was given except that unlike -- the unrecognized option will not be
  292. # removed from ARGV[].  Normally, an error value is returned in this case.
  293. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  294. # be found, so the number of remaining arguments is returned instead.
  295. # If OptChars is not a null string, it is the set of characters that indicate
  296. # that an argument is an option string if the string begins with one of the
  297. # characters.  A string consisting solely of two of the same option-indicator
  298. # characters stops the scanning of argv[].  The default is "-+".
  299. # argv[0] is not examined.
  300. # The number of arguments left in argc is returned.
  301. # If an error occurs, the global string OptErr is set to an error message
  302. # and a negative value is returned.
  303. # Current error values:
  304. # -1: option that required an argument did not get it.
  305. # -2: argument of incorrect type supplied for an option.
  306. # -3: unrecognized (invalid) option.
  307. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  308. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  309. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  310. {
  311. # ArgNum is the index of the argument being processed.
  312. # ArgsLeft is the number of arguments left in argv.
  313. # Arg is the argument being processed.
  314. # ArgLen is the length of the argument being processed.
  315. # ArgInd is the position of the character in Arg being processed.
  316. # Option is the character in Arg being processed.
  317. # Pos is the position in OptList of the option being processed.
  318. # NumOpt is true if a numeric option may be given.
  319.     ArgsLeft = argc
  320.     NumOpt = index(OptList,"&")
  321.     OptionNum = 0
  322.     if (OptChars == "")
  323.     OptChars = "-+"
  324.     while (OptChars != "") {
  325.     c = substr(OptChars,1,1)
  326.     OptChars = substr(OptChars,2)
  327.     OptCharSet[c]
  328.     OptTerm[c c]
  329.     }
  330.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  331.     Arg = argv[ArgNum]
  332.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  333.         break    # Not an option; quit
  334.     if (Arg in OptTerm) {
  335.         delete argv[ArgNum]
  336.         ArgsLeft--
  337.         break
  338.     }
  339.     ArgLen = length(Arg)
  340.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  341.         Option = substr(Arg,ArgInd,1)
  342.         if (NumOpt && Option ~ /[-+.0-9]/) {
  343.         # If this option is a numeric option, make its flag be & and
  344.         # its option string flag position be the position of & in
  345.         # the option string.
  346.         Option = "&"
  347.         Pos = NumOpt
  348.         # Prefix Arg with a char so that ArgInd will point to the
  349.         # first char of the numeric option.
  350.         Arg = "&" Arg
  351.         ArgLen++
  352.         }
  353.         # Find position of flag in option string, to get its type (if any).
  354.         # Disallow & as literal flag.
  355.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  356.         if (AllowUnrecOpt) {
  357.             Escape = 1
  358.             break
  359.         }
  360.         else {
  361.             OptErr = "Invalid option: " specGiven Option
  362.             return -3
  363.         }
  364.         }
  365.  
  366.         # Find what the value of the option will be if it takes one.
  367.         # NeedNextOpt is true if the option specifier is the last char of
  368.         # this arg, which means that if the option requires a value it is
  369.         # the next arg.
  370.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  371.         if (GotValue = ArgNum + 1 < argc)
  372.             Value = argv[ArgNum+1]
  373.         }
  374.         else {    # Value is included with option
  375.         Value = substr(Arg,ArgInd + 1)
  376.         GotValue = 1
  377.         }
  378.  
  379.         if (HadValue = AssignVal(Option,Value,Options,
  380.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  381.         specGiven)) {
  382.         if (HadValue < 0)    # error occured
  383.             return HadValue
  384.         if (HadValue == 2)
  385.             ArgInd++    # Account for the single-char value we used.
  386.         else {
  387.             if (NeedNextOpt) {    # option took next arg as value
  388.             delete argv[++ArgNum]
  389.             ArgsLeft--
  390.             }
  391.             break    # This option has been used up
  392.         }
  393.         }
  394.     }
  395.     if (Escape)
  396.         break
  397.     # Do not delete arg until after processing of it, so that if it is not
  398.     # recognized it can be left in ARGV[].
  399.     delete argv[ArgNum]
  400.     ArgsLeft--
  401.     }
  402.     if (compress != 0) {
  403.     dest = 1
  404.     src = argc - ArgsLeft + 1
  405.     for (count = ArgsLeft - 1; count; count--) {
  406.         ARGV[dest] = ARGV[src]
  407.         dest++
  408.         src++
  409.     }
  410.     }
  411.     return ArgsLeft
  412. }
  413.  
  414. # Assignment to values in Options[] occurs only in this function.
  415. # Option: Option specifier character.
  416. # Value: Value to be assigned to option, if it takes a value.
  417. # Options[]: Options array to return values in.
  418. # ArgType: Argument type specifier character.
  419. # GotValue: Whether any value is available to be assigned to this option.
  420. # Name: Name of option being processed.
  421. # OptionNum: Number of this option (starting with 1) if set in argv[],
  422. #     or 0 if it was given in a config file or in the environment.
  423. # SingleOpt: true if the value (if any) that is available for this option was
  424. #     given as part of the same command line arg as the option.  Used only for
  425. #     options from the command line.
  426. # specGiven is the option specifier character use, if any (e.g. - or +),
  427. # for use in error messages.
  428. # Global variables: OptErr
  429. # Return value: negative value on error, 0 if option did not require an
  430. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  431. # the arg.
  432. # Current error values:
  433. # -1: Option that required an argument did not get it.
  434. # -2: Value of incorrect type supplied for option.
  435. # -3: Bad type given for option &
  436. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  437. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  438.     # If option takes a value...    [
  439.     NumTypes = "*()#<>]"
  440.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  441.     OptErr = "Bad type given for & option"
  442.     return -3
  443.     }
  444.  
  445.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  446.     if (!GotValue) {
  447.         if (Name != "")
  448.         OptErr = "Variable requires a value -- " Name
  449.         else
  450.         OptErr = "option requires an argument -- " Option
  451.         return -1
  452.     }
  453.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  454.         OptErr = Err
  455.         return -2
  456.     }
  457.     # Mark this as a numeric variable; will be propogated to Options[] val.
  458.     if (ArgType != ":" && ArgType != ";")
  459.         Value += 0
  460.     if ((Instance = ++Options[Option,"count"]) > 1)
  461.         Options[Option,Instance] = Value
  462.     else
  463.         Options[Option] = Value
  464.     }
  465.     # If this is an environ or rcfile assignment & it was given a value...
  466.     else if (!OptionNum && Value != "") {
  467.     UsedValue = 1
  468.     # If the value is "0" or "-" and this is the first instance of it,
  469.     # do not set Options[Option]; this allows an assignment in an rcfile to
  470.     # turn off an option (for the simple "Option in Options" test) in such
  471.     # a way that it cannot be turned on in a later file.
  472.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  473.         Instance = 1
  474.     else
  475.         Instance = ++Options[Option]
  476.     # Save the value even though this is a flag
  477.     Options[Option,Instance] = Value
  478.     }
  479.     # If this is a command line flag and has a - following it in the same arg,
  480.     # it is being turned off.
  481.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  482.     UsedValue = 2
  483.     if (Option in Options)
  484.         Instance = ++Options[Option]
  485.     else
  486.         Instance = 1
  487.     Options[Option,Instance]
  488.     }
  489.     # If this is a flag assignment without a value, increment the count for the
  490.     # flag unless it was turned off.  The indicator for a flag being turned off
  491.     # is that the flag index has not been set in Options[] but it has an
  492.     # instance count.
  493.     else if (Option in Options || !((Option,1) in Options))
  494.     # Increment number of times this flag seen; will inc null value to 1
  495.     Instance = ++Options[Option]
  496.     Options[Option,"num",Instance] = OptionNum
  497.     return UsedValue
  498. }
  499.  
  500. # Option is the option letter
  501. # Value is the value being assigned
  502. # Name is the var name of the option, if any
  503. # ArgType is one of:
  504. # :    String argument
  505. # ;    Non-null string argument
  506. # *    Floating point argument
  507. # (    Non-negative floating point argument
  508. # )    Positive floating point argument
  509. # #    Integer argument
  510. # <    Non-negative integer argument
  511. # >    Positive integer argument
  512. # specGiven is the option specifier character use, if any (e.g. - or +),
  513. # for use in error messages.
  514. # Returns null on success, err string on error
  515. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  516.     if (ArgType == ":")
  517.     return ""
  518.     if (ArgType == ";") {
  519.     if (Value == "")
  520.         Err = "must be a non-empty string"
  521.     }
  522.     # A number begins with optional + or -, and is followed by a string of
  523.     # digits or a decimal with digits before it, after it, or both
  524.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  525.     Err = "must be a number"
  526.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  527.     Err = "may not include a fraction"
  528.     else if (ArgType ~ "[()<>]" && Value < 0)
  529.     Err = "may not be negative"
  530.     # (
  531.     else if (ArgType ~ "[)>]" && Value == 0)
  532.     Err = "must be a positive number"
  533.     if (Err != "") {
  534.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  535.     if (Name != "")
  536.         return ErrStr "variable " substr(Name,1,1) " " Err
  537.     else {
  538.         if (Option == "&")
  539.         Option = Value
  540.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  541.     }
  542.     }
  543.     else
  544.     return ""
  545. }
  546.  
  547. # Note: only the above functions are needed by ProcArgs.
  548. # The rest of these functions call ProcArgs() and also do other
  549. # option-processing stuff.
  550.  
  551. # Opts: Process command line arguments.
  552. # Opts processes command line arguments using ProcArgs()
  553. # and checks for errors.  If an error occurs, a message is printed
  554. # and the program is exited.
  555. #
  556. # Input variables:
  557. # Name is the name of the program, for error messages.
  558. # Usage is a usage message, for error messages.
  559. # OptList the option description string, as used by ProcArgs().
  560. # MinArgs is the minimum number of non-option arguments that this
  561. # program should have, non including ARGV[0] and +h.
  562. # If the program does not require any non-option arguments,
  563. # MinArgs should be omitted or given as 0.
  564. # rcFiles, if given, is a colon-seprated list of filenames to read for
  565. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  566. # by the value of the environment variable HOME.  If a filename begins with
  567. # $, the part from the character after the $ up until (but not including)
  568. # the first character not in [a-zA-Z0-9_] will be searched for in the
  569. # environment; if found its value will be substituted, if not the filename will
  570. # be discarded.
  571. # rcfiles are read in the order given.
  572. # Values given in them will not override values given on the command line,
  573. # and values given in later files will not override those set in earlier
  574. # files, because AssignVal() will store each with a different instance index.
  575. # The first instance of each variable, either on the command line or in an
  576. # rcfile, will be stored with no instance index, and this is the value
  577. # normally used by programs that call this function.
  578. # VarNames is a comma-separated list of variable names to map to options,
  579. # in the same order as the options are given in OptList.
  580. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  581. # searched for in the environment.  If set to -1, all values will be searched
  582. # for in the environment.  Values given in the environment will override
  583. # those given in the rcfiles but not those given on the command line.
  584. # NoRCopt, if given, is an additional letter option that if given on the
  585. # command line prevents the rcfiles from being read.
  586. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  587. # ExclusiveOptions() for a description of exOpts.
  588. # Special options:
  589. # If x is made an option and is given, some debugging info is output.
  590. # h is assumed to be the help option.
  591.  
  592. # Global variables:
  593. # The command line arguments are taken from ARGV[].
  594. # The arguments that are option specifiers and values are removed from
  595. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  596. # The number of elements in ARGV[] should be in ARGC.
  597. # After processing, ARGC is set to the number of elements left in ARGV[].
  598. # The option values are put in Options[].
  599. # On error, Err is set to a positive integer value so it can be checked for in
  600. # an END block.
  601. # Return value: The number of elements left in ARGV is returned.
  602. # Must keep OptErr global since it may be set by InitOpts().
  603. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  604. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  605.     if (MinArgs == "")
  606.     MinArgs = 0
  607.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  608.     optChars)
  609.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  610.     if (ArgsLeft >= 0) {
  611.         OptErr = "Not enough arguments"
  612.         Err = 4
  613.     }
  614.     else
  615.         Err = -ArgsLeft
  616.     printf "%s: %s.\nUse -h for help.\n%s\n",
  617.     Name,OptErr,Usage > "/dev/stderr"
  618.     exit 1
  619.     }
  620.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  621.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  622.     {
  623.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  624.     Err = -e
  625.     exit 1
  626.     }
  627.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  628.     {
  629.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  630.     Err = 1
  631.     exit 1
  632.     }
  633.     return ArgsLeft
  634. }
  635.  
  636. # ReadConfFile(): Read a file containing var/value assignments, in the form
  637. # <variable-name><assignment-char><value>.
  638. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  639. # line and whitespace between the variable name and the assignment character) 
  640. # is stripped.  Lines that do not contain an assignment operator or which
  641. # contain a null variable name are ignored, other than possibly being noted in
  642. # the return value.  If more than one assignment is made to a variable, the
  643. # first assignment is used.
  644. # Input variables:
  645. # File is the file to read.
  646. # Comment is the line-comment character.  If it is found as the first non-
  647. #     whitespace character on a line, the line is ignored.
  648. # Assign is the assignment string.  The first instance of Assign on a line
  649. #     separates the variable name from its value.
  650. # If StripWhite is true, whitespace around the value (whitespace between the
  651. #     assignment char and trailing whitespace on the line) is stripped.
  652. # VarPat is a pattern that variable names must match.  
  653. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  654. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  655. #     a line; no assignment operator is needed.  These variables are set in
  656. #     the output array with a null value.  Lines containing nothing but
  657. #     whitespace are still ignored.
  658. # Output variables:
  659. # Values[] contains the assignments, with the indexes being the variable names
  660. #     and the values being the assigned values.
  661. # Lines[] contains the line number that each variable occured on.  A flag set
  662. #     is record by giving it an index in Lines[] but not in Values[].
  663. # Return value:
  664. # If any errors occur, a string consisting of descriptions of the errors
  665. # separated by newlines is returned.  In no case will the string start with a
  666. # numeric value.  If no errors occur,  the number of lines read is returned.
  667. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  668. FlagsOK,
  669. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  670.     if (Comment != "")
  671.     Comment = "^" Comment
  672.     AssignLen = length(Assign)
  673.     if (VarPat == "")
  674.     VarPat = "."    # null varname not allowed
  675.     while ((Status = (getline Line < File)) == 1) {
  676.     LineNum++
  677.     sub("^[ \t]+","",Line)
  678.     if (Line == "")        # blank line
  679.         continue
  680.     if (Comment != "" && Line ~ Comment)
  681.         continue
  682.     if (Pos = index(Line,Assign)) {
  683.         Var = substr(Line,1,Pos-1)
  684.         Val = substr(Line,Pos+AssignLen)
  685.         if (StripWhite) {
  686.         sub("^[ \t]+","",Val)
  687.         sub("[ \t]+$","",Val)
  688.         }
  689.     }
  690.     else {
  691.         Var = Line    # If no value, var is entire line
  692.         Val = ""
  693.     }
  694.     if (!FlagsOK && Val == "") {
  695.         Errs = Errs \
  696.         sprintf("\nBad assignment on line %d of file %s: %s",
  697.         LineNum,File,Line)
  698.         continue
  699.     }
  700.     sub("[ \t]+$","",Var)
  701.     if (Var !~ VarPat) {
  702.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  703.         LineNum,File,Var)
  704.         continue
  705.     }
  706.     if (!(Var in Lines)) {
  707.         Lines[Var] = LineNum
  708.         if (Pos)
  709.         Values[Var] = Val
  710.     }
  711.     }
  712.     if (Status)
  713.     Errs = Errs "\nCould not read file " File
  714.     close(File)
  715.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  716. }
  717.  
  718. # Variables:
  719. # Data is stored in Options[].
  720. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  721. # Global vars:
  722. # Sets OptErr.  Uses ENVIRON[].
  723. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  724. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  725. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  726. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  727.     split("",filesRead,"")    # make awk know this is an array
  728.     NumVars = split(VarNames,Vars,",")
  729.     TypesInd = Ret = 0
  730.     if (EnvSearch == -1)
  731.     EnvSearch = NumVars
  732.     for (i = 1; i <= NumVars; i++) {
  733.     Var = Vars[i]
  734.     CharOpt = substr(OptList,++TypesInd,1)
  735.     if (CharOpt ~ "^[:;*()#<>&]$")
  736.         CharOpt = substr(OptList,++TypesInd,1)
  737.     Map[Var] = CharOpt
  738.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  739.     # Do not overwrite entries from environment
  740.     if (i <= EnvSearch && Var in ENVIRON &&
  741.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  742.         return Err
  743.     }
  744.  
  745.     numrcFiles = split(rcFiles,fNames,":")
  746.     for (i = 1; i <= numrcFiles; i++) {
  747.     rcFile = fNames[i]
  748.     if (rcFile ~ "^~/")
  749.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  750.     else if (rcFile ~ /^\$/) {
  751.         rcFile = substr(rcFile,2)
  752.         match(rcFile,"^[a-zA-Z0-9_]*")
  753.         envvar = substr(rcFile,1,RLENGTH)
  754.         if (envvar in ENVIRON)
  755.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  756.         else
  757.         continue
  758.     }
  759.     if (rcFile in filesRead)
  760.         continue
  761.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  762.     # may be the same
  763.     filesRead[rcFile]
  764.     if ("x" in Options)
  765.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  766.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  767.     if (retStr > 0)
  768.         READ_RCFILE = 1
  769.     else if (ret != "") {
  770.         OptErr = retStr
  771.         Ret = -1
  772.     }
  773.     for (Var in Lines)
  774.         if (Var in Map) {
  775.         if ((Err = AssignVal(Map[Var],
  776.         Var in Values ? Values[Var] : "",Options,Types[Var],
  777.         Var in Values,Var,0)) < 0)
  778.             return Err
  779.         }
  780.         else {
  781.         OptErr = sprintf(\
  782.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  783.         Lines[Var],rcFile)
  784.         Ret = -1
  785.         }
  786.     }
  787.  
  788.     if ("x" in Options)
  789.     for (Var in Map)
  790.         if (Map[Var] in Options)
  791.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  792.         "/dev/stderr"
  793.         else
  794.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  795.     return Ret
  796. }
  797.  
  798. # OptSets is a semicolon-separated list of sets of option sets.
  799. # Within a list of option sets, the option sets are separated by commas.  For
  800. # each set of sets, if any option in one of the sets is in Options[] AND any
  801. # option in one of the other sets is in Options[], an error string is returned.
  802. # If no conflicts are found, nothing is returned.
  803. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  804. # the exclusions presented by the first set of sets (ab,def,g) if:
  805. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  806. # (a or b is in Options[]) AND (g is in Options) OR
  807. # (d, e, or f is in Options[]) AND (g is in Options)
  808. # An error will be returned due to the exclusions presented by the second set
  809. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  810. # todo: make options given on command line unset options given in config file
  811. # todo: that they conflict with.
  812. function ExclusiveOptions(OptSets,Options,
  813. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  814. SetNum,OSetNum) {
  815.     NumSetSets = split(OptSets,SetSets,";")
  816.     # For each set of sets...
  817.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  818.     # NumSets is the number of sets in this set of sets.
  819.     NumSets = split(SetSets[SetSet],Sets,",")
  820.     # For each set in a set of sets except the last...
  821.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  822.         s1 = Sets[SetNum]
  823.         L1 = length(s1)
  824.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  825.         # If any of the options in this set was given, check whether
  826.         # any of the options in the other sets was given.  Only check
  827.         # later sets since earlier sets will have already been checked
  828.         # against this set.
  829.         if ((c1 = substr(s1,Pos1,1)) in Options)
  830.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  831.             s2 = Sets[OSetNum]
  832.             L2 = length(s2)
  833.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  834.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  835.                 ErrStr = ErrStr "\n"\
  836.                 sprintf("Cannot give both %s and %s options.",
  837.                 c1,c2)
  838.             }
  839.     }
  840.     }
  841.     if (ErrStr != "")
  842.     return substr(ErrStr,2)
  843.     return ""
  844. }
  845.  
  846. # The value of each instance of option Opt that occurs in Options[] is made an
  847. # index of Set[].
  848. # The return value is the number of instances of Opt in Options.
  849. function Opt2Set(Options,Opt,Set,  count) {
  850.     if (!(Opt in Options))
  851.     return 0
  852.     Set[Options[Opt]]
  853.     count = Options[Opt,"count"]
  854.     for (; count > 1; count--)
  855.     Set[Options[Opt,count]]
  856.     return count
  857. }
  858.  
  859. # The value of each instance of option Opt that occurs in Options[] that
  860. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  861. # Other values are made indexes of Set[].
  862. # The return value is the number of instances of Opt in Options.
  863. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  864.     ret = Opt2Set(Options,Opt,aSet)
  865.     for (value in aSet)
  866.     if (substr(value,1,1) == "!")
  867.         nSet[substr(value,2)]
  868.     else
  869.         Set[value]
  870.     return ret
  871. }
  872.  
  873. # Returns true if option Opt was given on the command line.
  874. function CmdLineOpt(Options,Opt,  i) {
  875.     for (i = 1; (Opt,"num",i) in Options; i++)
  876.     if (Options[Opt,"num",i] != 0)
  877.         return 1
  878.     return 0
  879. }
  880. ### End of ProcArgs library
  881.  
  882.